//---------------------------------------------------------------------
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// 
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Reflection;

namespace RedmineWorkerDotNET.App.Controls
{
    /// <summary>
    /// Represents a DataGridViewTextBoxColumn with a drop-down filter list accessible from the header cell.  
    /// </summary>
    public class DataGridViewAutoFilterTextBoxColumn : DataGridViewTextBoxColumn
    {
        /// <summary>
        /// Initializes a new instance of the DataGridViewAutoFilterTextBoxColumn class.
        /// </summary>
        public DataGridViewAutoFilterTextBoxColumn() : base()
        {
            base.DefaultHeaderCellType = typeof(DataGridViewAutoFilterColumnHeaderCell);
            base.SortMode = DataGridViewColumnSortMode.Programmatic;
        }

        #region public properties that hide inherited, non-virtual properties: DefaultHeaderCellType and SortMode

        /// <summary>
        /// Returns the AutoFilter header cell type. This property hides the 
        /// non-virtual DefaultHeaderCellType property inherited from the 
        /// DataGridViewBand class. The inherited property is set in the 
        /// DataGridViewAutoFilterTextBoxColumn constructor. 
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Type DefaultHeaderCellType
        {
            get
            {
                return typeof(DataGridViewAutoFilterColumnHeaderCell);
            }
        }

        /// <summary>
        /// Gets or sets the sort mode for the column and prevents it from being 
        /// set to Automatic, which would interfere with the proper functioning 
        /// of the drop-down button. This property hides the non-virtual 
        /// DataGridViewColumn.SortMode property from the designer. The inherited 
        /// property is set in the DataGridViewAutoFilterTextBoxColumn constructor.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Advanced), Browsable(false)]
        [DefaultValue(DataGridViewColumnSortMode.Programmatic)]
        public new DataGridViewColumnSortMode SortMode
        {
            get
            {
                return base.SortMode;
            }
            set
            {
                if (value == DataGridViewColumnSortMode.Automatic)
                {
                    throw new InvalidOperationException(
                        "A SortMode value of Automatic is incompatible with " +
                        "the DataGridViewAutoFilterColumnHeaderCell type. " +
                        "Use the AutomaticSortingEnabled property instead.");
                }
                else
                {
                    base.SortMode = value;
                }
            }
        }

        #endregion

        #region public properties: FilteringEnabled, AutomaticSortingEnabled, DropDownListBoxMaxLines

        /// <summary>
        /// Gets or sets a value indicating whether filtering is enabled for this column. 
        /// </summary>
        [DefaultValue(true)]
        public Boolean FilteringEnabled
        {
            get
            {
                // Return the header-cell value.
                return ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell)
                    .FilteringEnabled;
            }
            set
            {
                // Set the header-cell property. 
                ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell)
                    .FilteringEnabled = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether automatic sorting is enabled for this column. 
        /// </summary>
        [DefaultValue(true)]
        public Boolean AutomaticSortingEnabled
        {
            get
            {
                // Return the header-cell value.
                return ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell)
                    .AutomaticSortingEnabled;
            }
            set
            {
                // Set the header-cell property.
                ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell)
                    .AutomaticSortingEnabled = value;
            }
        }

        /// <summary>
        /// Gets or sets the maximum height of the drop-down filter list for this column. 
        /// </summary>
        [DefaultValue(20)]
        public Int32 DropDownListBoxMaxLines
        {
            get
            {
                // Return the header-cell value.
                return ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell)
                    .DropDownListBoxMaxLines;
            }
            set
            {
                // Set the header-cell property.
                ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell)
                    .DropDownListBoxMaxLines = value;
            }
        }

        #endregion public properties

        #region public, static, convenience methods: RemoveFilter and GetFilterStatus

        /// <summary>
        /// Removes the filter from the BindingSource bound to the specified DataGridView. 
        /// </summary>
        /// <param name="dataGridView">The DataGridView bound to the BindingSource to unfilter.</param>
        public static void RemoveFilter(DataGridView dataGridView)
        {
            DataGridViewAutoFilterColumnHeaderCell.RemoveFilter(dataGridView);
        }

        /// <summary>
        /// Gets a status string for the specified DataGridView indicating the 
        /// number of visible rows in the bound, filtered BindingSource, or 
        /// String.Empty if all rows are currently visible. 
        /// </summary>
        /// <param name="dataGridView">The DataGridView bound to the 
        /// BindingSource to return the filter status for.</param>
        /// <returns>A string in the format "x of y records found" where x is 
        /// the number of rows currently displayed and y is the number of rows 
        /// available, or String.Empty if all rows are currently displayed.</returns>
        public static String GetFilterStatus(DataGridView dataGridView)
        {
            return DataGridViewAutoFilterColumnHeaderCell.GetFilterStatus(dataGridView);
        }

        #endregion
    }
    
    public class SimpleFilteredList<T> : BindingList<T>, IBindingListView
    {

        public SimpleFilteredList() { }



        #region Searching

        protected override bool SupportsSearchingCore
        {

            get
            {

                return true;

            }

        }

        protected override int FindCore(PropertyDescriptor prop, object key)
        {

            return FindCore(0, prop, key);

        }

        protected int FindCore(int startIndex, PropertyDescriptor prop, object key)
        {

            // Get the property info for the specified property.

            PropertyInfo propInfo = typeof(T).GetProperty(prop.Name);

            T item;

            if (key != null)
            {

                // Loop through the items to see if the key

                // value matches the property value.

                for (int i = startIndex; i < Count; ++i)
                {

                    item = (T)Items[i];

                    if (propInfo.GetValue(item, null).Equals(key))

                        return i;

                }

            }

            return -1;

        }

        public int Find(int startIndex, string property, object key)
        {

            // Check the properties for a property with the specified name.

            PropertyDescriptorCollection properties =

            TypeDescriptor.GetProperties(typeof(T));

            PropertyDescriptor prop = properties.Find(property, true);

            // If there is not a match, return -1 otherwise pass search to

            // FindCore method.

            if (prop == null)

                return -1;

            else

                if (startIndex <= 0)

                    return FindCore(prop, key);

                else

                    return FindCore(startIndex, prop, key);

        }

        #endregion Searching

        #region Sorting

        ArrayList sortedList;

        ArrayList unsortedItems;

        bool isSortedValue;

        ListSortDirection sortDirectionValue;

        PropertyDescriptor sortPropertyValue;

        protected override bool SupportsSortingCore
        {

            get { return true; }

        }

        protected override bool IsSortedCore
        {

            get { return isSortedValue; }

        }

        protected override PropertyDescriptor SortPropertyCore
        {

            get { return sortPropertyValue; }

        }

        protected override ListSortDirection SortDirectionCore
        {

            get { return sortDirectionValue; }

        }



        public void ApplySort(string propertyName, ListSortDirection direction)
        {

            // Check the properties for a property with the specified name.

            PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(T))[propertyName];

            // If there is not a match, return -1 otherwise pass search to

            // FindCore method.

            if (prop == null)

                throw new ArgumentException(propertyName +

                " is not a valid property for type:" + typeof(T).Name);

            else

                ApplySortCore(prop, direction);

        }

        protected override void ApplySortCore(PropertyDescriptor prop,

        ListSortDirection direction)
        {

            RaiseListChangedEvents = false;

            sortedList = new ArrayList();

            // Check to see if the property type we are sorting by implements

            // the IComparable interface.

            Type interfaceType = prop.PropertyType.GetInterface("IComparable");

            if (interfaceType != null)
            {

                // If so, set the SortPropertyValue and SortDirectionValue.

                sortPropertyValue = prop;

                sortDirectionValue = direction;

                unsortedItems = new ArrayList(this.Count);

                // Loop through each item, adding it the the sortedItems ArrayList.

                foreach (Object item in this.Items)
                {

                    sortedList.Add(prop.GetValue(item));

                    unsortedItems.Add(item);

                }

                // Call Sort on the ArrayList.

                sortedList.Sort();

                T temp;

                // Check the sort direction and then copy the sorted items

                // back into the list.

                if (direction == ListSortDirection.Descending)

                    sortedList.Reverse();

                for (int i = 0; i < this.Count; i++)
                {

                    int position = Find(0, prop.Name, sortedList[i]);

                    if (position != i)
                    {

                        temp = this[i];

                        this[i] = this[position];

                        this[position] = temp;

                    }

                }

                RaiseListChangedEvents = true;

                isSortedValue = true;

                // Raise the ListChanged event so bound controls refresh their

                // values.

                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

            }

            else

                // If the property type does not implement IComparable, let the user

                // know.

                throw new NotSupportedException("Cannot sort by " + prop.Name +

                ". This" + prop.PropertyType.ToString() +

                " does not implement IComparable");

        }

        protected override void RemoveSortCore()
        {

            int position;

            object temp;

            RaiseListChangedEvents = false;

            // Ensure the list has been sorted.

            if (unsortedItems != null)
            {

                // Loop through the unsorted items and reorder the

                // list per the unsorted list.

                for (int i = 0; i < unsortedItems.Count; )
                {

                    position = this.Find(0, SortPropertyCore.Name,

                    unsortedItems[i].GetType().

                    GetProperty(SortPropertyCore.Name).GetValue(unsortedItems[i], null));

                    if (position >= 0 && position != i)
                    {

                        temp = this[i];

                        this[i] = this[position];

                        this[position] = (T)temp;

                        i++;

                    }

                    else if (position == i)

                        i++;

                    else

                        // If an item in the unsorted list no longer exists, delete it.

                        unsortedItems.RemoveAt(i);

                }

                RaiseListChangedEvents = true;

                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

            }

        }

        public void RemoveSort()
        {

            RemoveSortCore();

        }

        public override void EndNew(int itemIndex)
        {

            // Check to see if the item is added to the end of the list,

            // and if so, re-sort the list.

            if (sortPropertyValue != null && itemIndex > 0 && itemIndex == this.Count - 1)

                ApplySortCore(this.sortPropertyValue, this.sortDirectionValue);

            base.EndNew(itemIndex);

        }

        #endregion Sorting

        #region AdvancedSorting

        public bool SupportsAdvancedSorting
        {

            get { return false; }

        }

        public ListSortDescriptionCollection SortDescriptions
        {

            get { return null; }

        }

        public void ApplySort(ListSortDescriptionCollection sorts)
        {

            throw new NotImplementedException();

        }

        #endregion AdvancedSorting

        #region Filtering

        private string filterValue = "";

        /// <summary>

        /// Should be in the format columnName ='desiredValue'

        /// </summary>

        private string filterPropertyNameValue;

        private Object filterCompareValue;

        List<T> unfilteredListValue = new List<T>();

        public List<T> UnfilteredList
        {

            get { return unfilteredListValue; }

        }

        public bool SupportsFiltering
        {

            get { return true; }

        }



        public void RemoveFilter()
        {

            if (Filter != null) Filter = null;

        }

        public string FilterPropertyName
        {

            get { return filterPropertyNameValue; }

        }

        public Object FilterCompare
        {

            get { return filterCompareValue; }

        }



        public string Filter
        {

            get
            {

                return filterValue;

            }

            set
            {

                if (filterValue != value)
                {

                    RaiseListChangedEvents = false;

                    // If filter value is null, reset list.

                    if (value == null)
                    {

                        this.ClearItems();

                        foreach (T t in unfilteredListValue)

                            this.Items.Add(t);

                        filterValue = value;

                    }

                     // If the value is empty string, do nothing.

                     // This behavior is compatible with DataGridView

                     // AutoFilter code.

                    else if (value == "") 
                    {
                        this.ClearItems();

                        foreach (T t in unfilteredListValue)

                            this.Items.Add(t);

                        filterValue = value;
                    }



                    // If the value is not null or string, than process

                    // normal.

                    else if (Regex.Matches(value,

                     "[?[\\w ]+]? ?[=] ?'?[\\w|/: ]+'?",

                     RegexOptions.Singleline).Count == 1 && value != "")
                    {

                        // If the filter is not set.

                        unfilteredListValue.Clear();

                        unfilteredListValue.AddRange(this.Items);

                        filterValue = value;

                        GetFilterParts();

                        ApplyFilter();

                    }

                    else if (Regex.Matches(value,

                      "[?[\\w ]+]? ?[=] ?'?[\\w|/: ]+'?",

                      RegexOptions.Singleline).Count > 1)

                        throw new ArgumentException("Multi-column" +

                          "filtering is not implemented.");

                    else throw new ArgumentException("Filter is not" +

                           "in the format: propName = 'value'.");

                    RaiseListChangedEvents = true;

                    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

                }

            }

        }

        void FilteredListView_ListChanged(object sender, ListChangedEventArgs e)
        {

            MessageBox.Show(RaiseListChangedEvents.ToString());

            // Add the new item

            if (e.ListChangedType == ListChangedType.ItemAdded)

                unfilteredListValue.Add(this[e.NewIndex]);

            // Remove the new item

            if (e.ListChangedType == ListChangedType.ItemDeleted)

                unfilteredListValue.RemoveAt(e.NewIndex);

        }



        private void ApplyFilter()
        {

            unfilteredListValue.Clear();

            unfilteredListValue.AddRange(this.Items);

            List<T> results = new List<T>();

            PropertyDescriptor propDesc = TypeDescriptor.GetProperties(typeof(T))[FilterPropertyName];

            if (propDesc != null)
            {

                int tempResults = -1;

                do
                {

                    tempResults = FindCore(tempResults + 1, propDesc, FilterCompare);

                    if (tempResults != -1)

                        results.Add(this[tempResults]);

                } while (tempResults != -1);

            }

            this.ClearItems();

            if (results != null && results.Count > 0)
            {

                foreach (T itemFound in results)

                    this.Add(itemFound);

            }

        }



        public void GetFilterParts()
        {

            string[] filterParts = Filter.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);

            filterPropertyNameValue = filterParts[0].Replace("[", "").Replace("]", "").Trim();

            PropertyDescriptor propDesc =

            TypeDescriptor.GetProperties(typeof(T))[filterPropertyNameValue.ToString()];

            if (propDesc != null)
            {

                try
                {

                    TypeConverter converter = TypeDescriptor.GetConverter(propDesc.PropertyType);

                    filterCompareValue = converter.ConvertFromString(filterParts[1].Replace("'", "").Trim());

                }

                catch (NotSupportedException)
                {

                    throw new ArgumentException("Specified filter value " +

                    FilterCompare + " can not be converted from string." +

                        "..Implement a type converter for " + propDesc.PropertyType.ToString());

                }

            }

            else throw new ArgumentException("Specified property '" +

               FilterPropertyName + "' is not found on type " +

               typeof(T).Name + ".");

        }

        #endregion Filtering

    }
}